package edu.hawaii.ics414.blackjack.player;

import edu.hawaii.ics414.blackjack.card.Card;
import edu.hawaii.ics414.blackjack.card.GameDeck;
import edu.hawaii.ics414.blackjack.card.Rank;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Test;

/**
 * Tests the Player class as well as the inherited Seat class.
 * Since Seat is an abstract method, it is somewhat difficult to test directly,
 * so this test will also test the Seat methods.
 * 
 * @author Branden Ogata
 *
 */

public class TestPlayer
{
  /**
   * Tests the methods in the Player class.
   * 
   */
  
  @Test
  public void test()
  {
    Player player = new Player(100);
    GameDeck deck = new GameDeck(6);
    
    // The player should have 100 money
    assertEquals("Check getMoney", 100, player.getMoney());
    
    // Count the number of cards before and after giving the Player two cards
    assertEquals("Check countNumberOfCards before giving cards", 0, player.countNumberOfCards(0));
    player.startHand(deck);
    assertEquals("Check countNumberOfCards after giving cards", 2, player.countNumberOfCards(0));
    
    makePlayableHand(player, deck);
    
    // Test hitting
    assertTrue("Check canHit", player.canHit(0));
    
    // Keep hitting until going over 21
    while (player.sum(0) < 21)
    {
      // If there are five cards in the hand, then reset
      if (player.countNumberOfCards(0) == 5)
      {
        makePlayableHand(player, deck);
      }
      else
      {
        player.hit(deck, 0);
      }
    }
    
    // Hitting should no longer be possible
    assertFalse("Check canHit after reaching 21 or above", player.canHit(0));
    
    // Test standing
    makePlayableHand(player, deck);
    assertTrue("Check canStand", player.canStand(0));
    player.stand(0);
    assertFalse("Check canStand after standing", player.canStand(0));
    
    // Test doubling down
    makePlayableHand(player, deck);
    assertTrue("Check canDoubleDown", player.canDoubleDown(0));
    player.doubleDown(deck, 0);
    assertFalse("Check canDoubleDown after standing", player.canDoubleDown(0));
    
    // Test splitting
    makeSplittableHand(player, deck);
    assertTrue("Check canSplit", player.canSplit(0));
    player.split(deck, 0);
    assertEquals("First hand has two cards", 2, player.countNumberOfCards(0));
    assertEquals("Second hand has two cards", 2, player.countNumberOfCards(1));
    
    // Test betting
    player.clear();

    try
    {
      player.setBet(0, 256);
      fail();
    }
    catch (IllegalArgumentException e)
    {
      assertEquals("Cannot set bet higher than money available", "Not enough money for this bet.", 
                                                                 e.getMessage());
    }
    
    player.setBet(0, 20);
    player.startHand(deck);
    assertEquals("Check getBet, setBet", 20, player.getBet(0));
    assertEquals("Check getMoney", 80, player.getMoney());
    
    
    // Cannot bet after standing
    player.stand(0);
    
    try
    {
      player.setBet(0, 15);
      fail();
    }
    catch (IllegalArgumentException e)
    {
      assertEquals("Cannot set bet after standing", 
                   "It is too late to change the bet for this hand.", e.getMessage());
    }
    
    // Winning the hand should increase the money of the player by that amount
    player.wonRound(0);
    assertEquals("Check money after winning round", 120, player.getMoney());
    
    // Losing the hand should decrease the money of the player by that amount
    player.clear();
    player.setBet(0, 10);
    player.startHand(deck);
    player.stand(0);
    player.lostRound(0);
    assertEquals("Check money after losing round", 110, player.getMoney());
    
    // Betting after hitting should fail
    do
    {
      player.clear();
      player.setBet(0, 10);
      player.startHand(deck);
      player.hit(deck, 0);
      
      try
      {
        player.setBet(0, 15);
        fail();
      }
      catch (IllegalArgumentException e)
      {
        assertEquals("Cannot set bet after playing a given hand", 
                     "It is too late to change the bet for this hand.", e.getMessage());
        player.tiedRound(0);
      }
    } while (player.sum(0) > 21);

    // Draws should result in no net change to the money of the player
    player.tiedRound(0);
    assertEquals("Check money after tying round", 110, player.getMoney());
    
    // Test getCards for the Player class
    String cards = player.getCards();
    
    for (Card c : player.hands[0])
    {
      if (c != null)
      {
        assertTrue("Check getCards", cards.contains(c.toString()));
      }
    }
  }

  /**
   * Ensures that the player has a hand summing to less than 21.
   * 
   * @param player The Seat to test.
   * @param deck The GameDeck to get Card instances from.
   * 
   */
  
  protected static void makePlayableHand(Seat player, GameDeck deck)
  {
    while ((player.sum(0) >= 21) || (!player.canStand(0)))
    {
      player.clear();
      player.startHand(deck);
    }    
  }
  
  /**
   * Ensures that the player has a splittable hand.
   * 
   * @param player The Seat to test.
   * @param deck The GameDeck to get Card instances from.
   * 
   */
  
  protected static void makeSplittableHand(Seat player, GameDeck deck)
  {
    player.clear();
    Card first = deck.peek();
    player.hit(deck, 0);
    
    while (!deck.peek().getRank().equals(first.getRank()))
    {
      deck.draw();
    }
    
    player.hit(deck, 0);  
  }
  
  /**
   * Provides the given Seat with a hand that has the value specified in the parameter.
   * To make matters simpler, this will limit the hand to two cards.
   * 
   * @param player The Seat to provide with a hand.
   * @param deck The GameDeck to get Card instances from.
   * @param value The int equal to the value that the player hand must have.
   * 
   */
  
  protected static void makeHandOfValue(Seat player, GameDeck deck, int value)
  {
    while ((player.sum(0) != value) && !((player.hasAce(0)) && (player.sum(0) - 10 == value)))
    {
      // If there are no cards, then cannot reach the value on this first card
      if (player.countNumberOfCards(0) == 0)
      {
        // If is it impossible to reach the value in one card, then get a 10
        if (value > 11)
        {
          if (deck.peek().getValue() == 10)
          {
            player.hit(deck, 0);
          }
          else
          {
            deck.draw();
          }
        }
        // Else if the value is 11, then get any card other than an Ace or 10-value
        else if (value == 11)
        {
          if ((deck.peek().getValue() == 10) || (deck.peek().getRank().equals(Rank.ACE)))
          {
            deck.draw();
          }
          else
          {
            player.hit(deck, 0);
          }
        }
        // Else get any card that is less than the value
        else if (deck.peek().getValue() < value)
        {
          player.hit(deck, 0);
        }
        else
        {
          deck.draw();
        }
      }
      // Else it is the second card, so must reach the value on this round
      else if (player.countNumberOfCards(0) == 1)
      {
        // Get the difference between the value and the current card;
        // if negative, then an Ace is probably being counted high
        int difference = value - player.sum(0);
        
        if ((difference <= 0) && (player.hasAce(0)))
        {
          difference = value - (player.sum(0) - 10);
        }

        // If the difference is 11, then get an Ace
        if ((difference == 11) && (deck.peek().getRank().equals(Rank.ACE)))
        {
          player.hit(deck, 0);
        }
        // If the value is equal to the difference, then get the card
        else if (deck.peek().getValue() == difference)
        {
          player.hit(deck, 0);
        }
        else
        {
          deck.draw();
        }
      }
      // Else there are too many cards, so reset
      else
      {
        player.clear();
      }

/*      
      int difference = value - player.sum(0);

      // If the difference is 11, then get an Ace or a 6 depending on the circumstances
      if (difference == 11)
      {
        // If the value is supposed to be 11, then get any card that is not an Ace or value 10
        if ((value == 11) && ((deck.peek().getValue() != 10) && 
                              (!deck.peek().getRank().equals(Rank.ACE))))
        {
          player.hit(deck, 0);
        }
        else if (deck.peek().getRank().equals(Rank.ACE))  
        {
          player.hit(deck, 0);
        }
        else
        {
          deck.draw();
        }
      }
      // Else if the difference between the required value 
      // and the value of the hand is greater than 10,
      // then get a Card of value 10
      else if ((value - player.sum(0) >= 10) && (deck.peek().getValue() == 10))
      {
        player.hit(deck, 0);
      }
      // Else get a Card with value equal to the difference
      else if (deck.peek().getValue() == difference)
      {
        player.hit(deck, 0);
      }
      else
      {
        deck.draw();
      }
*/      
    }
  }
  
  /**
   * Creates a soft hand that has the given value.
   * 
   * @param player The Seat to provide with a hand.
   * @param deck The GameDeck to get Card instances from.
   * @param value The int equal to the value that the player hand must have.
   * 
   */
  
  protected static void makeSoftHand(Seat player, GameDeck deck, int value)
  {
    player.clear();      

    // Keep on getting a new hand until the hand is soft
    while ((player.countNumberOfCards(0) != 2) || (!player.isHandSoft(0) || 
           ((player.sum(0) != value) && (player.sum(0) - 10 != value)) || (player.canSplit(0))))
    {
      player.clear();
      
      // A soft hand must have an Ace
      while (!deck.peek().getRank().equals(Rank.ACE))
      {
        deck.draw();
      }
      player.hit(deck, 0);
      
      makeHandOfValue(player, deck, value);
    }
  }
  
  /**
   * Creates a hard hand that has the given value.
   * 
   * @param player The Seat to provide with a hand.
   * @param deck The GameDeck to get Card instances from.
   * @param value The int equal to the value that the player hand must have.
   * 
   */
  
  protected static void makeHardHand(Seat player, GameDeck deck, int value)
  {
    player.clear();      

    // Keep on getting a new hand while the hand is soft
    while ((player.countNumberOfCards(0) != 2) || (player.hasAce(0)) || 
           (player.sum(0) != value) || (player.canSplit(0)))
    {
      player.clear();      
      makeHandOfValue(player, deck, value);
    }
  }
  
  /**
   * Creates a splittable hand with a given rank for the cards.
   * 
   * @param player The Seat to test.
   * @param deck The GameDeck to get Card instances from.
   * @param rank The Rank that the Card instances much have.
   * 
   */
  
  protected static void makeSplittableHand(Seat player, GameDeck deck, Rank rank)
  {
    do
    {
      player.clear();
      makeSplittableHand(player, deck);
    } while ((player.getHand(0)[0] != null) && (!player.getHand(0)[0].getRank().equals(rank)) ||
             (player.getHand(0)[1] != null) && (!player.getHand(0)[1].getRank().equals(rank)));
  }

}
